package cz.muni.fi.pb138.bookinfo;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class JIBsearch {

    private String sessionId;
    private Integer numberOfResults;
    private static final int LIMIT = 10;

    /**
     * Converts a string into URL and establishes a connection then parses a URL and XML Document
     *
     * @param strUrl String to be converted into URL
     * @return opened Document as XML document
     */
    private Document stringToDoc(String strUrl) {

    	URL url = null;
        try {
            url = new URL(strUrl);
        } catch (MalformedURLException e) {
            throw new ServiceFailureException("Wrong internet adress");
        }
        URLConnection con;
        try {
            con = url.openConnection();
        } catch (IOException ex) {
            throw new ServiceFailureException("Cannot connect to JIB.cz");
        }
        con.setRequestProperty("Content-Type", "text/xml");
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db;
        try {
            db = dbf.newDocumentBuilder();
        } catch (ParserConfigurationException ex) {
            throw new ServiceFailureException("Internal error");
        }
        Document doc;
        try {
            doc = db.parse(con.getInputStream());
        } catch (IOException ex) {
            throw new ServiceFailureException("Connection failed");
        } catch (SAXException ex) {
            throw new ServiceFailureException("Cannot parse xml returned from JIB");
        }
        return doc;
    }

    /**
     * login() method - logs in JIB and get SessionID of current connection
     *
     * @return String SessionID of connection
     */
    private void login() {
    	Properties acc = new Properties();
        try {
            acc.load(JIBsearch.class.getResourceAsStream("acc.properties"));
        } catch (IOException e) {}
                
        String strUrl = "http://www.jib.cz/X";
        strUrl += "?op=login_request&user_name=" + acc.getProperty("username");
        strUrl += "&user_password=" + acc.getProperty("password");
        /*ResourceBundle bundle = ResourceBundle.getBundle("JIBsearch.acc");
        String strUrl = "http://www.jib.cz/X";
        strUrl += "?op=login_request&user_name=" + bundle.getString("username");
        strUrl += "&user_password=" + bundle.getString("password");*/
        Document doc = stringToDoc(strUrl);

        NodeList sess_id_list = doc.getElementsByTagName("session_id");
        if (sess_id_list.getLength() != 1) {
            throw new ServiceFailureException("Incorrect login to JIB");
        }
        Element sess_id = (Element) sess_id_list.item(0);
        this.sessionId = sess_id.getTextContent();
    }

    /**
     * Based on given session ID, ISBN and title of book - searches recources (all 8 are set) and returna HashSet of those, who have 1 or more records
     * with such ISBN May return empty set
     *
     * No need to check for ISBN correct form, that is performed earlier
     *
     * @param isbn book ISBN
     * @param title book title
     * @return Set of positove matching resources, or empty Set
     */
    private Map<String, Integer> syncFind(String isbn, String title) {
        if (sessionId == null || sessionId.isEmpty()) {
            login();
        }
        if (sessionId == null || sessionId.isEmpty()) {
            throw new IllegalArgumentException("Incorrect sessionId");
        }

        Map<String, Integer> resourceMap = new HashMap<String, Integer>();

        String strUrl = "http://www.jib.cz/X";
        String partOne;
        if (title != null) {
            try {
                title = URLEncoder.encode(title, "UTF-8");
            } catch (UnsupportedEncodingException ex) {
                Logger.getLogger(JIBsearch.class.getName()).log(Level.SEVERE, null, ex);
            }
            title = title.replace('+', ' ');
            title = title.replaceAll(" ", "%20");
            partOne = "?op=find_request&wait_flag=Y&find_request_command=WTI=(" + title + ")%20OR%20";
        } else {
            partOne = "?op=find_request&wait_flag=Y&find_request_command=";
        }
        //part two contains the resources....e.g. find_base_001=CNL01950 indicated one resource to be searched at
        String partTwo = "ISBN=(" + isbn + ")&find_base_001=CNL03210&find_base_001=CNL01950&find_base_001=CNL03075&find_base_001=CNL03060&find_base_001=CNL02984&find_base_001=CNL03134&find_base_001=CNL03205&find_base_001=CNL01948&session_id=" + sessionId;
        
        Document doc = stringToDoc(strUrl + partOne + partTwo);

        NodeList base_infoList = doc.getElementsByTagName("base_info");
        for (int i = 0; i < base_infoList.getLength(); i++) {
            Element baseInfo = (Element) base_infoList.item(i);
            if (baseInfo.getTextContent().equalsIgnoreCase("ERROR")) {
                continue;
            }
            if (baseInfo.getElementsByTagName("no_of_documents").item(0).getTextContent().equalsIgnoreCase("000000000")) {
                continue;
            }

            try {
                numberOfResults = Integer.parseInt(baseInfo.getElementsByTagName("no_of_documents").item(0).getTextContent());
            } catch (NumberFormatException e) {
                throw new ServiceFailureException("Internal error");
            }
            resourceMap.put(baseInfo.getElementsByTagName("set_number").item(0).getTextContent(), numberOfResults);
        }
        return resourceMap;
    }

    /**
     * This method takes the ISBN and title of the book which availibility we search and returns the Set of all libraries
     *
     * @param isbn ISBN of the book
     * @param title book's title
     * @return Set of all libraries in republic which have the book, Set can be empty
     */
    public Set<String> retrieveLibraries(String isbn, String title) {
    	Map<String, Integer> resourceMap = syncFind(isbn, title);
        if(resourceMap.isEmpty() && title != null){
        	resourceMap.putAll(syncFind(isbn, null));
        }
        Set<String> libraries = new HashSet<String>();
        if (this.sessionId == null || this.sessionId.isEmpty()) {
            login();
        }
        for (String setNumber : resourceMap.keySet()) {
            int limit = resourceMap.get(setNumber);
            if (limit > LIMIT) {
                limit = LIMIT;
            }
            for (int entry = 1; entry <= limit; entry++) {
                String stringUrl = "http://www.jib.cz/X?op=present_request&set_number=" + setNumber + "&set_entry=" + String.format("%08d", entry) + "&format=marc&view=full&session_id=" + this.sessionId;
                Document doc = stringToDoc(stringUrl);

                NodeList nodeList = doc.getElementsByTagName("datafield");
                for (int i = 0; i < nodeList.getLength(); i++) {
                    Element element = (Element) nodeList.item(i);
                    if ("910".equals((element).getAttribute("tag"))) {
                        Element subfield = (Element) element.getElementsByTagName("subfield").item(0);
                        libraries.add(subfield.getTextContent());
                    }
                }
                NodeList newSessionId = doc.getElementsByTagName("session_id");
                this.sessionId = newSessionId.item(0).getTextContent();
            }
        }
        return Collections.unmodifiableSet(refactorLibraries(libraries));
    }

    /**
     * Given the Set of all libraries this method removes unnecessary characters and returns the Set
     *
     * @param libraries Set of libraries with unnecessary characters t obe removed
     * @return Set of libraries to be presented to the user, list can be empty
     */
    private Set<String> refactorLibraries(Set<String> libraries) {
        Set<String> refactoredList = new HashSet<String>();
        for (String library : libraries) {
            if (!(library.contains("["))) {
                continue;
            }
            String[] splitByComma = library.split(",");
            for (int i = 0; i < splitByComma.length; i++) {
                String[] splitBySlash = splitByComma[i].split("/");
                refactoredList.add((String) splitBySlash[1].subSequence(2, splitBySlash[1].length() - 1));
            }
        }
        return Collections.unmodifiableSet(refactoredList);
    }


    /*
     * Tests these our completely newly invented features!
     */
    public static void main(String[] args) {

        JIBsearch test = new JIBsearch();
        // final String for testing purposes - Hunger Games
        String isbn = "978-0439023481";
        // secondary final string - Babicka
        String isbn2 = "978-8020016003";
        //third example Body in the library
        String isbn3 = "978-1405868013";
        //fourth example - Švejk
        String isbn4 = "978-8024221793";
        // Harry potter a kámen mudrců
        String isbn5 = "978800007885";

        System.out.println(test.retrieveLibraries(isbn5,"Harry Potter a kámen mudrců"));
        System.out.println(test.retrieveLibraries(isbn5, null));
        System.out.println("\n");
        System.out.println(test.retrieveLibraries(isbn,"Hunger games"));
        System.out.println(test.retrieveLibraries(isbn,null));
        System.out.println("\n");
        System.out.println(test.retrieveLibraries(isbn4,"Osudy dobrého vojáka Švejka za světové války"));
        System.out.println(test.retrieveLibraries(isbn4,null));
        System.out.println("\n");
        System.out.println(test.retrieveLibraries(isbn2, "Babička"));
        System.out.println(test.retrieveLibraries(isbn2,null));
        System.out.println("\n");
        System.out.println(test.retrieveLibraries(isbn3,"Body in the library"));
        System.out.println(test.retrieveLibraries(isbn3,null));

    }
}